package my.spring.ioc;

import my.spring.aop.annotation.Aop;
import my.spring.ioc.annotation.Bean;
import my.spring.ioc.annotation.Config;
import my.spring.ioc.annotation.Inject;
import my.spring.ioc.bean.BeanDefinition;
import my.spring.ioc.bean.BeanDefinitionRegistry;
import my.spring.ioc.exception.InitBeanDefinitionException;
import my.spring.ioc.exception.InitBeanException;
import my.spring.ioc.exception.NoSuchBeanDefinitionException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * BeanFactory注解实现类
 * @author fengbo
 * @date 2018/3/20
 */
public class AnnotationBeanFactory implements BeanFactory, BeanDefinitionRegistry {

    protected final Map<String, Object> beanMap = new ConcurrentHashMap<>(64);
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);

    private Properties applicationProperties;
    private ClassLoader classLoader;

    public AnnotationBeanFactory() {
        applicationProperties = new Properties();
        classLoader = Thread.currentThread().getContextClassLoader();
        initApplicationProperties();
        initBeanDefinitions(getBaseUrl(applicationProperties.getProperty("base_package")));
        initConfig();
    }

    /**
     * 初始化要扫描的包
     */
    private void initApplicationProperties() {
        InputStream in = classLoader.getResourceAsStream("application.properties");
        try {
            applicationProperties.load(in);
        } catch (IOException e) {
            throw new InitBeanDefinitionException("Property Load Error! ", e);
        }
    }

    /**
     * 根据包名初始化所有的bean
     * @param basePackage 包名
     */
    private String getBaseUrl(String basePackage) {
        URL url = classLoader.getResource(basePackage.replace(".", "/"));
        if (url == null) {
            throw new InitBeanDefinitionException("Init Beans Error, Base Package Not Exist!");
        }
        return url.getPath();
    }

    /**
     * 根据路径解析出所有的BeanDefinition并注入到容器中
     * @param filePath 路径
     */
    private void initBeanDefinitions(String filePath) {
        File file = new File(filePath);
        File[] childFiles = file.listFiles();
        if (childFiles == null) {
            return;
        }
        for (File childFile : childFiles) {
            if (childFile.isDirectory()) {
                initBeanDefinitions(childFile.getPath());
                continue;
            }
            String childFilePath = childFile.getPath();
            if (!childFilePath.endsWith(".class")) {
                continue;
            }
            childFilePath = childFilePath.substring(childFilePath.indexOf("classes") + 8,
                    childFilePath.lastIndexOf("."));
            childFilePath = childFilePath.replace(System.getProperty("file.separator"), ".");
            try {
                Class<?> clazz = classLoader.loadClass(childFilePath);
                if (clazz != null && clazz.isAnnotationPresent(Bean.class)) {
                    BeanDefinition beanDefinition = new BeanDefinition();
                    beanDefinition.setName(clazz.getAnnotation(Bean.class).value());
                    beanDefinition.setClassName(childFilePath);
                    beanDefinition.setClazz(clazz);
                    if (beanDefinition.getClazz().isAnnotationPresent(Aop.class)) {
                        beanDefinition.setProxy(true);
                    }
                    Bean bean = clazz.getAnnotation(Bean.class);
                    registerBeanDefinition(bean.value(), beanDefinition);

                }
            } catch (ClassNotFoundException e) {
                throw new InitBeanDefinitionException("Create Bean Error! ", e);
            }
        }
    }

    /**
     * 初始化配置类
     */
    private void initConfig() {
        for (String key : getDefinitionNames()) {
            BeanDefinition beanDefinition = getBeanDefinition(key);
            Class clazz = beanDefinition.getClazz();
            if (clazz.isAnnotationPresent(Config.class)) {
                Object obj = getBean(key);
                Method[] methods = clazz.getMethods();
                try {
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(Bean.class)) {
                            Class<?>[] parameterTypes = method.getParameterTypes();
                            Object[] parameters = new Object[parameterTypes.length];
                                beanMap.put(method.getAnnotation(Bean.class).value(),
                                        method.invoke(obj, parameters));
                        }
                    }
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new InitBeanException("Init Config Failed!", e);
                }
            }
        }
    }

    /*************************************************
     * BeanFactory接口的实现
     ************************************************/

    @Override
    public Object getBean(String beanName) {
        if (beanMap.containsKey(beanName)) {
            return beanMap.get(beanName);
        }
        if (!containsBeanDefinition(beanName)) {
            throw new NoSuchBeanDefinitionException(beanName);
        }
        BeanDefinition beanDefinition = getBeanDefinition(beanName);
        Object bean;
        try {
            bean = beanDefinition.getClazz().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new InitBeanDefinitionException(e.getMessage(), e);
        }
        Field[] fields = beanDefinition.getClazz().getDeclaredFields();
        for (Field field : fields) {
            if (field == null || !field.isAnnotationPresent(Inject.class)) {
                continue;
            }
            String name = field.getAnnotation(Inject.class).value();
            Object obj = getBean(name);
            // 允许访问private字段
            field.setAccessible(true);
            // 把引用对象注入属性
            try {
                field.set(bean, obj);
            } catch (IllegalAccessException e) {
                throw new InitBeanException("Inject Bean Error! ", e);
            }
        }
        beanMap.put(beanName, bean);
        return bean;
    }

    /************************************************
     * BeanDefinitionRegistry接口实现
     *************************************************/

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public void removeBeanDefinition(String beanName) {
        if (beanDefinitionMap.remove(beanName) == null) {
            throw new NoSuchBeanDefinitionException(beanName);
        }
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        BeanDefinition bd = beanDefinitionMap.get(beanName);
        if (bd == null) {
            throw new NoSuchBeanDefinitionException(beanName);
        }
        return bd;
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public String[] getDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[beanDefinitionMap.size()]);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }
}
